// 16.4 可变参数模板
/**
 * 一个可变参数模板（variadic template）就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包（parameter packet）。存在两种参数包：
 *   - 模板参数包（template parameter packet），表示零个或多个模板参数；
 *   - 函数参数包（function parameter packet），表示零个或多个函数参数。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include <functional>
#include "../VisualStudio2012/15/Quote.h"
#include "../VisualStudio2012/12/TextQuery.h"

int main()
{
    // 16.4 可变参数模板
    /*
我们用一个省略号来指出一个模板参数或函数参数表示一个包。在一个模板参数列表中，class…或typename…指出接下来的参数表示零个或多个类型的列表；
  一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数的列表。在函数参数列表中，如果一个参数的类型是一个模板参数包，则此参数也是一个函数参数包。
例如：
// Args是个模板参数包；rest是个函数参数包
template<typename T, typename... Args>
void foo(const T& t, const Args&... rest);
与往常一样，编译器从函数的实参推断模板参数类型。对于一个可变参数模板，编译器还会推断包中参数的数目。例如，给定下面的调用：
int i = 0; double d = 3.14; string s = "how now brown cow";
foo(i, s, 42, d); // 包中有3个参数
foo(i, 42, "hi"); // 包中有2个参数
foo(d, s);        // 保中有1个参数
foo("hi");        // 空包
编译器会为foo实例化出四个不同的版本：
void foo(const int&, const string&, const int&, const double&);
void foo(const int&, const int&, const char[3]&);
void foo(const double&, const string&);
void foo(const char[3]&);
    */

    // sizeof…运算符
    // 当我们需要知道包中有多少元素时，可以使用sizeof…运算符。类似sizeof（参见4.9节，第139页），sizeof…也返回一个常量表达式（参见2.4.4节，第58页），而且不会对其实参求值：
    /*
template<typename ... Args> void g(Args ... args)
{
  cout << sizeof...(Args) << endl; // 类型参数的数目
  cout << sizeof...(args) << endl; // 函数参数的数目
}
    */

    // 16.4.1 编写可变参数函数模板
    /*
如6.2.6节（第198页）所述，我们可以使用一个initializer_list来定义一个可接受可变数目实参的函数。但是，所有实参必须具有相同的类型（或它们的类型可以转换为同一个公共类型）。
  当我们既不知道想要处理的实参的数目也不知道它们的类型时，可变参数函数是很有用的。作为一个例子，我们将定义一个函数，它类似较早的error_msg函数，差别仅在于新函数实参的类型也是可变的。
可变参数函数通常是递归的（参见6.3.2节，第204页）。第一步调用处理包中的第一个实参，然后用剩余实参调用自身。我们的print函数也是这样的模式，每次递归调用将第二个实参打印到第一个实参表示的流中。
  为了终止递归，我们还需要定义一个非可变参数的print函数，它接受一个流和一个对象：
// 用来终止递归并打印最后一个元素的函数】
// 此函数必须在可变参数版本的print定义之前声明
template<typename T>
ostream &print(ostream &os, const T &t)
{
  return os << t; // 包中最后一个元素之后不打印分隔符
}
template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args &... rest)
{
  os << t << ", ";           // 打印第一个实参
  return print(os, rest...); // 递归调用，打印其他实参
}
我们的可变参数版本的print函数接受三个参数：一个ostream&，一个const T&和一个参数包。而此调用只传递了两个实参。其结果是rest中的第一个实参被绑定到t，剩余实参形成下一个print调用的参数包。
  因此，在每个调用中，包中的第一个实参被移除，成为绑定到t的实参。
    */
    // 当定义可变参数版本的print时，非可变参数版本的声明必须在作用域中。否则，可变参数版本会无限递归。

    // 16.4.2 包扩展
    /*
对于一个参数包，除了获取其大小外，我们能对它做的唯一的事情就是扩展（expand）它。当扩展一个包时，我们还要提供用于每个扩展元素的模式（pattern）。
  扩展一个包就是将它分解为构成的元素，对每个元素应用模式，获得扩展后的列表。我们通过在模式右边放一个省略号（…）来触发扩展操作。
例如，我们的print函数包含两个扩展：
template<typename T, typename... Args>
ostream &print(ostream &, const T& t, const Args&... rest) // 扩展Args
{
  os << t << ", ";
  return print(os, rest...);                               // 扩展rest
}
第一个扩展操作扩展模板参数包，为print生成函数参数列表。第二个扩展操作出现在对print的调用中。此模式为print调用生成实参列表。
  对Args的扩展中，编译器将模式const Arg&应用到模板参数包Args中的每个元素。因此，此模式的扩展结果是一个逗号分隔的零个或多个类型的列表，每个类型都形如const type&。
    */

    // 理解包扩展
    /*
print中的函数参数包扩展仅仅将包扩展为其构成元素，C++语言还允许更复杂的扩展模式。
  例如，我们可以编写第二个可变参数函数，对其每个实参调用debug_rep（参见16.3节，第615页），然后调用print打印结果string：[插图]
// 在print调用中对每个实参调用debug_rep
template<typename... Args>
ostream &errorMsg(ostream &os, const Args&... rest)
{
  // print(os, debug_rep(a1), debug_rep(a2),... debug_rep(an))
  return print(os, debug_rep(rest)...);
}
这个print调用使用了模式debug_reg（rest）。此模式表示我们希望对函数参数包rest中的每个元素调用debug_rep。扩展结果将是一个逗号分隔的debug_rep调用列表。
与之相对，下面的模式会编译失败：
// 将包传递给debug_rep；print(os, debug_rep(a1, a2, ..., an))
print(os, debug_rep(rest...)); // 错误：此调用无匹配函数
    */

    // 16.4.3 转发参数包
    // 在新标准下，我们可以组合使用可变参数模板与forward机制来编写函数，实现将其实参不变地传递给其他函数。
    /*
作为例子，我们将为StrVec类（参见13.5节，第465页）添加一个emplace_back成员。标准库容器的emplace_back成员是一个可变参数成员模板（参见16.1.4节，第596页），
  它用其实参在容器管理的内存空间中直接构造一个元素。
如我们所见，保持类型信息是一个两阶段的过程。首先，为了保持实参中的类型信息，必须将emplace_back的函数参数定义为模板类型参数的右值引用（参见16.2.7节，第613页）：
class StrVec {
public:
  template<class... Args> void emplace_back(Args&&... args);
  // 其他成员的定义，同13.5节
};
模板参数包扩展中的模式是&&，意味着每个函数参数将是一个指向其对应实参的右值引用。
其次，当emplace_back将这些实参传递给construct时，我们必须使用forward来保持实参的原始类型（参见16.2.7节，第614页）：
template <class... Args>
inline
void StrVec::emplace_back(Args&&... args)
{
  chk_n_alloc(); // 如果需要的话重新分配StrVec内存空间
  alloc.construct(first_free++, std::forward<Args>(args)...);
}
    */
    
    // 建议：转发和可变参数模板
    // 可变参数函数通常将它们的参数转发给其他函数。这种函数通常具有与我们的emplace_back函数一样的形式：
    /*
// fun有零个或多个参数，每个参数都是一个模板参数类型的右值引用
template<typename... Args>
void fun(Args&&... args) // 将Args扩展为一个右值引用的列表
{
  // work的实参既扩展Args又扩展args
  work(std::forward<Args>(args)...);
}
这里我们希望将fun的所有实参转发给另一个名为work的函数，假定由它完成函数的实际工作。类似emplace_back中对construct的调用，work调用中的扩展既扩展了模板参数包也扩展了函数参数包。
由于fun的参数是右值引用，因此我们可以传递给它任意类型的实参；由于我们使用std：：forward传递这些实参，因此它们的所有类型信息在调用work时都会得到保持。
    */

    return 0;
}